package model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.HashSet;
import java.util.Observable;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * FileParser is the link between the XML data and the creation of the objects used in the Model.  For most
 * conceivable queries, the FileParser will return lists of users, papers and conferences to the user.
 * @author Kevin Anderson
 *
 */
public class FileParser extends Observable {

	/**
	 * A constant value to use as an object ID to ensure a new value is generated.
	 */
	public static final int NEW_ELEMENT = -1;
	
	/**
	 * The location of the current file associated with the DOM.
	 */
	private String current_data_path;
	
	/**
	 * A Document object to parse through.
	 */
	private Document doc;


	/*
	 * NO CONSTRUCTOR REQUIRED.  USING JAVA'S OUT OF BOX CONSTRUCTOR FOR FILEPARSER
	 */


	/**
	 * isValidData() will reconstruct the parsed file to determine if the file is currently valid
	 * and parsable.
	 * @return <code>true</code> if data is valid and parsable, <code>false</code> otherwise.
	 */
	public boolean isValidData() {	
		return init(current_data_path);
	}

	/**
	 * Starts up a new parser with the given path.  Throws an IOException if the filename is bad, settin
	 * @param given_path A string that represents the name of the datafile.
	 * @return <code>true</code> if the filename provided is valid,  <code>false</code> otherwise.
	 */
	public boolean init(String given_path)
	{
		DocumentBuilderFactory domFact = DocumentBuilderFactory.newInstance();
		domFact.setNamespaceAware(true);
		DocumentBuilder build;

		try {
			build = domFact.newDocumentBuilder();
		} catch (Exception e) {
			return false;
		}

		try {
			doc = build.parse(given_path);
		} catch (Exception e) {
			System.err.println("There was an error locating the file.");
			return false;
		}

		current_data_path = given_path;
		return true;
	}

	/**
	 * Searches the data for all conferences to which a user has access.
	 * @param user_id The ID of the user in which to search.
	 * @return An array of Conference objects or an empty conference array of no conferences exist.
	 */
	public Conference[] getConferencesForUser(int user_id)
	{

		//GET A LIST OF CONFERENCES TO WHICH A USER HAS ACCESS
		String userQuery = "//data/assignments/assignment[@userID="+user_id+"]";
		NodeList nodes = null;

		try {
			nodes = getNode(userQuery);
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		if (nodes.getLength() > 0) {//I HAVE FOUND SOME CONFERENCES
			//PREPARE THE ARRAY TO RECEIVE THE CONFERENCES
			HashSet<Integer> confSet = new HashSet<Integer>();
			
			//FOR EACH CONFERENCE, CREATE A NEW CONFERENCE FROM DATA IN THE FILE
			for (int i = 0; i < nodes.getLength(); i++)
			{
				NamedNodeMap attributes = nodes.item(i).getAttributes();
				confSet.add(Integer.parseInt(attributes.getNamedItem("confID").getNodeValue().toString()));
			}
			
			Conference[] confs = new Conference[confSet.size()];
			int i = 0;
			for (int c : confSet)
			{
				confs[i] = getConfWithID(c);
				i++;
			}
			
			return confs;

		} else {
			return new Conference[0];
		}
	}
	
	/**
	 * Returns a user object associated with the id provided.
	 * @param id The ID of the user desired.
	 * @return A user object associated with the user ID provided.  If no user exists, will return <code>null</code>
	 */
	public User getUserWithID(int id)
	{

		String userQuery = "//data/users/user[@id="+id+"]";
		NodeList userNodes = null;
		try {
			userNodes = getNode(userQuery);
		} catch (XPathExpressionException e) {
			//TODO Throw error of query is bad.
			e.printStackTrace();
		}

		if(userNodes.getLength() > 0)
		{
			NamedNodeMap userInfo = userNodes.item(0).getAttributes();

//			String fullName = userInfo.getNamedItem("fullName").getNodeValue().toString();
			String userName = userInfo.getNamedItem("userName").getNodeValue().toString();
			String email    = userInfo.getNamedItem("email").getNodeValue().toString();
			return new User(id, userName, email);
		}
		else
		{
			System.err.println("No users found");
			return null;
		}
	}

	/**
	 * Returns a user object associated with the username provided.
	 * @param username The username of the user being searched for.
	 * @return The first user object associated with the username provided.  If no user exists, will return <code>null</code>
	 */
	public User getUserWithUserName(String username)
	{

		String userQuery = "//data/users/user[@userName=\"" + username.toLowerCase() +"\"]";
		NodeList userNodes = null;
		try {
			userNodes = getNode(userQuery);
		} catch (XPathExpressionException e) {
			//TODO Throw error of query is bad.
			e.printStackTrace();
		}

		if (userNodes.getLength() > 0)
		{
			NamedNodeMap userInfo = userNodes.item(0).getAttributes();

//			String fullName = userInfo.getNamedItem("fullName").getNodeValue().toString();
			String ID 		= userInfo.getNamedItem("id").getNodeValue().toString();
			String email    = userInfo.getNamedItem("email").getNodeValue().toString();
			return new User(Integer.parseInt(ID), username, email);
		}
		else
		{
			System.err.println("No User Found.");
			return null;
		}
	}


	/**
	 * Returns a list of Users with role SPC for a specified conference.
	 * @param conf_id The conference ID to pull SPCs for a conference.
	 * @return An array of user objects.  Will pass an empty array of no users exist for that conference.
	 */
	public User[] getSPCsForConf(int confID) {

		String spcQuery = "//data/assignments/assignment[@roleID=\"3\" and @confID="+confID+"]";
		NodeList spcNodes = null;
		try {
			spcNodes = getNode(spcQuery);
		} catch (XPathExpressionException e) {
			//TODO Throw error of query is bad.
			e.printStackTrace();
		}	

		User[] users = new User[spcNodes.getLength()];
		for(int i = 0; i < spcNodes.getLength(); i++)
		{
			Element e = (Element) spcNodes.item(i);
			int thisUserID = Integer.parseInt(e.getAttribute("userID"));
			users[i] = getUserWithID(thisUserID);
			users[i].setRole(Role.SPC);
		}
		
		return users;
	}

	/**
	 * Searches for Roles available to a user for a specific Conference.
	 * @param userID The user ID o the user to search for.
	 * @param confID The conference ID of the conference to search in.
	 * @return An array of roles that are available for a user in a certain conference.  
	 * If no roles are available, will return an empty array. 
	 */
	public Role[] getRolesForUser(int userID, int confID)
	{

		String roleQuery = "//data/assignments/assignment[@userID="+userID+" and @confID="+confID+"]";
		NodeList roleNodes = null;
		try {
			roleNodes = getNode(roleQuery);
		} catch (XPathExpressionException e) {
			//TODO Throw error of query is bad.
			e.printStackTrace();
		}	

		Role[] roles = new Role[roleNodes.getLength()];

		for(int i = 0; i < roleNodes.getLength(); i++)
		{
			Element e = (Element) roleNodes.item(i);
			String singleRoleQ = "//data/roles/role[@id="+e.getAttribute("roleID")+"]";
			String value = null;
			try {
				value = getValue(singleRoleQ, "name");	
			} catch (XPathExpressionException err) {
				err.printStackTrace();
			}

			roles[i] = Role.valueOf(value);
		}
		return roles;
	}

	/**
	 * Returns a manusrcipt array of all manuscripts associated with a specific user for a specific conference.
	 * @param userID The user ID of the author.
	 * @param confID The conf ID of the conference.
	 * @return Returns an array of manuscript objects, if no manuscripts exists, it returns an empty array.
	 */
	public Manuscript[] getPapersByAuthor(int userID, int confID)
	{
		String papQuery = "//data/papers/paper[@authorID="+userID+" and @confID="+confID+"]";
		NodeList papNodes = null;

		try{
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (papNodes.getLength() > 0) {
			Manuscript[] manuscripts = new Manuscript[papNodes.getLength()];
			for (int i = 0; i < papNodes.getLength(); i++)
			{
				Element e = (Element) papNodes.item(i);
				manuscripts[i] = getPaperWithID(Integer.parseInt(e.getAttribute("id")));
			}
			return manuscripts;			
		} else {
			System.err.println("There were no manuscripts by that author");
			return new Manuscript[0];
		}	
	}

	/**
	 * Returns a manuscript array of all manuscripts associated with a specific conference.
	 * @param confID the ConferenceID for which to search for manuscripts.
	 * @return returns an array of manuscript objects, if no manuscripts exists, it returns an empty array.
	 */
	public Manuscript[] getPapersByConference(int confID)
	{
		String papQuery = "//data/papers/paper[@confID="+confID+"]";

		NodeList papNodes = null;
		try {
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (papNodes.getLength() > 0) {
			Manuscript[] manuscripts = new Manuscript[papNodes.getLength()];
			for (int i = 0; i < papNodes.getLength(); i++)
			{
				Element e = (Element) papNodes.item(i);
				manuscripts[i] = getPaperWithID(Integer.parseInt(e.getAttribute("id")));
			}
			return manuscripts;
		} else {
			System.err.println("There were no manuscripts for that conference");
			return new Manuscript[0];
		}		
	}

	/**
	 * Returns a manuscript array of all manuscripts associated with a specific subprogram chair for a given conference.
	 * @param spcID The ID of the subprogram chair
	 * @param confID THe ID of the conference.
	 * @return returns an array of manuscript objects, if no manuscript exists, it returns an empty array.
	 */
	public Manuscript[] getPapersBySPC(int spcID, int confID)
	{
		String papQuery = "//data/papers/paper[@spcID="+spcID+" and @confID="+confID+"]";

		NodeList papNodes = null;
		try {
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (papNodes.getLength() > 0) {
			Manuscript[] manuscripts = new Manuscript[papNodes.getLength()];
			for (int i = 0; i < papNodes.getLength(); i++)
			{
				Element e = (Element) papNodes.item(i);
				manuscripts[i] = getPaperWithID(Integer.parseInt(e.getAttribute("id")));
			}
			return manuscripts;
		} else {
			System.err.println("There were no papers for that subProgram Chair");
			return new Manuscript[0];
		}		
	}
	
	/**
	 * Returns a {@link Model.manuscript} array of all manuscripts associated
	 * with a specific reviewer for a certain conference.
	 * @param reviewerID The ID of the reviewer.
	 * @param confID THe ID of the conference.
	 * @return returns an array of manuscript objects, if no manuscript exists, it returns an empty array.
	 */

	public Manuscript[] getPapersByReviewer(int reviewerID, int confID)
	{
		String papQuery = "//data/papers/paper[@confID="+confID+"]/reviewer[@userID="+reviewerID+"]";
		NodeList revNodes = null;

		try {
			revNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (revNodes.getLength() > 0)
		{
			Manuscript[] manuscript = new Manuscript[revNodes.getLength()];
			for (int i = 0; i < revNodes.getLength(); i++)
			{
				Element e = (Element) revNodes.item(i).getParentNode();
				manuscript[i] = getPaperWithID(Integer.parseInt(e.getAttribute("id")));
			}
			return manuscript;
		} else {
			System.err.println("There were no papers assigned to this user.");
			return new Manuscript[0];
		}		
	}

	/**
	 * Returns a User array of all reviewers available for a certain conference.
	 * @param confID The conference in which to search.
	 * @return returns an array of User objects, if no Reviewers exists, it returns an empty array.
	 */

	public User[] getReviewersByConf(int confID)
	{
		String assgnQuery = "//data/assignments/assignment[@confID=\""+confID+"\"]";
		NodeList assgnNodes = null;

		try {
			assgnNodes = getNode(assgnQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (assgnNodes.getLength() > 0)
		{
			User[] users = new User[assgnNodes.getLength()];
			for (int i = 0; i < assgnNodes.getLength(); i++)
			{
				Element e = (Element) assgnNodes.item(i);
				int userID = Integer.parseInt(e.getAttribute("userID"));
				users[i] = getUserWithID(userID);
				users[i].setRole(Role.REVIEWER);
			}
			return users;
		} else {
			System.err.println("There were no reviews to return");
			return new User[0];
		}		
	
		
	}
	
	/**
	 * Returns a User array of all users assigned to review a paper.
	 * @param paperID The ID of the paper for which to search.
	 * @return Returns a User array of all users assigned to review a paper.
	 * If no users exist returns an empty array.
	 */
	public User[] getReviewersByPaperID(int paperID)
	{
		String revQuery = "//data/papers/paper[@id=\""+paperID+"\"]/reviewer";
		NodeList revNodes = null;

		try {
			revNodes = getNode(revQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (revNodes.getLength() > 0)
		{
			User[] users = new User[revNodes.getLength()];
			for (int i = 0; i < revNodes.getLength(); i++)
			{
				Element e = (Element) revNodes.item(i);
				int reviewerID = Integer.parseInt(e.getAttribute("userID"));
				users[i] = getUserWithID(reviewerID);
				users[i].setRole(Role.REVIEWER);
			}
			return users;
		} else {
			System.err.println("There were no reviews to return");
			return new User[0];
		}		
	
	}

	/**
	 * Returns a count of papers assigned to a reviewer for a conference
	 * @param confID The conference to search.
	 * @param userID The user for review assignments to searcn for
	 */
	public int getReviewCountForReviewer(int confID, int userID)
	{
		String revQuery = "//data/papers/paper[@confID=\""+confID+"\"]/reviewer[@userID=\""+userID+"\"]";
		NodeList revNodes = null;

		try {
			revNodes = getNode(revQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		return (revNodes.getLength());

	}
	
	/**
	 * Returns a count of papers assigned to a SPC for a conference
	 * @param confID The conference to search.
	 * @param userID The user for review assignments to searcn for
	 */
	public int getAssignCountForSPC(int confID, int userID)
	{
		String revQuery = "//data/papers/paper[@confID=\""+confID+"\" and @spcID=\""+userID+"\"]";
		NodeList revNodes = null;

		try {
			revNodes = getNode(revQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		return (revNodes.getLength());

	}

	
	/**
	 * Returns an array of reviews of a paper ID.
	 * @param paperID The ID of the paper for which the reviews are desired.
	 * @return An array of reviews of paperID, if no reviews exist, returns an empty array.
	 */
	public Review[] getReviewsByPaperID(int paperID)
	{
		String revQuery = "//data/reviews/review[@paperID=\""+paperID+"\"]";
		NodeList revNodes = null;

		try {
			revNodes = getNode(revQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (revNodes.getLength() > 0)
		{
			Review[] reviews = new Review[revNodes.getLength()];
			for (int i = 0; i < revNodes.getLength(); i++)
			{
				Element e = (Element) revNodes.item(i);
				int id = Integer.parseInt(e.getAttribute("id"));
				int reviewerID = Integer.parseInt(e.getAttribute("userID"));
				String path = e.getAttribute("path");
				reviews[i] = new Review(id, paperID, reviewerID, path); 
			}

			return reviews;
		} else {
			System.err.println("There were no reviews to return");
			return new Review[0];
		}		
	}

	/**
	 * Returns an array of recommendations of a paper ID.
	 * @param paperID The ID of the paper for which the recommdations are desired.
	 * @return An array of recommendations of paperID, if no recommendations exist, returns an empty array.
	 */
	public Recommendation[] getRecommendationByPaperID(int paperID)
	{
		String revQuery = "//data/recommendations/recommendation[@paperID="+paperID+"]";
		NodeList recNodes = null;

		try {
			recNodes = getNode(revQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		if (recNodes.getLength() > 0)
		{
			Recommendation[] recommendations = new Recommendation[recNodes.getLength()];
			for (int i = 0; i < recNodes.getLength(); i++)
			{
				Element e = (Element) recNodes.item(i);
				int id = Integer.parseInt(e.getAttribute("id"));
				int userID = Integer.parseInt(e.getAttribute("userID"));
				String recValue = e.getAttribute("recValue");
				String path = e.getAttribute("path");
				recommendations[i] = new Recommendation(id, paperID, userID, recValue, path); 
			}

			return recommendations;
		} else {
			System.err.println("There were no recommendations to return");
			return new Recommendation[0];
		}		
	}

	/**
	 * Updates a user, or if user ID does not exist, writes a new user.
	 * @param user
	 * @return <code>true</code> if the user was written successfully to the parser, <code>false</code> otherwise.
	 */
	public boolean writeUser(User user)
	{
		if (user.getID() != FileParser.NEW_ELEMENT)
		{
			String userQuery = "//data/users/user[@id="+user.getID()+"]";
			NodeList userNodes = null;
			try {
				userNodes = getNode(userQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}


			if(userNodes.getLength() > 0) {
				//THERE WAS A USERNODE FOUND, UPDATE IT
				Element userInfo = (Element) userNodes.item(0);
				userInfo.setAttribute("id", Integer.toString(user.getID()));
				userInfo.setAttribute("fullName","");
				userInfo.setAttribute("name", user.getName().toLowerCase());
				userInfo.setAttribute("email", user.getEmail().toLowerCase());
			} else {
				System.err.println("No User By that ID was found, cannot update");
				return false;
			}
		} else { //CREATE THE NEW USER!
			String userQ = "//data/users";

			NodeList userData = null;
			try {
				userData = getNode(userQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			if (userData.getLength() > 0) {
				Element userElement = (Element) userData.item(0);
				Element newUser = doc.createElement("user");
				newUser.setAttribute("id", Integer.toString(nextUserID()));
				newUser.setAttribute("name", user.getName());
				newUser.setAttribute("email", user.getEmail());
				newUser.setAttribute("fullName","");
				userElement.appendChild(newUser);
			} else {
				return false;
			}

		}


		setChanged();
		notifyObservers();
		return saveDOM();
	}


	/**
	 * Write a given conference to persistence.  Will accept modifications to existing conferences or
	 * new conferences entirely.
	 * @param conf The conference to write to persistence.
	 * @return <code>true</code> if the confernce was written successfully to the parser, <code>false</code> otherwise.
	 */
	public boolean writeConference(Conference conf)
	{
		//check to see if conference exists
		if (conf.getConfID() != FileParser.NEW_ELEMENT)
		{
			String confQuery = "//data/conferences/conference[@id="+conf.getConfID()+"]";
			NodeList confNodes = null;
			try {
				confNodes = getNode(confQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			if(confNodes.getLength() > 0) {
				//A CONFERENCE EXISTS UPDATE IT
				Element confInfo = (Element) confNodes.item(0);
				confInfo.setAttribute("decisionUTC", Long.toString(conf.getDecisionDL().getTime()/1000));
				confInfo.setAttribute("enddateUTC", Long.toString(conf.getEndDate().getTime()/1000));
				confInfo.setAttribute("reccomendUTC", Long.toString(conf.getRecommendationDL().getTime()/1000));
				confInfo.setAttribute("reviewUTC", Long.toString(conf.getReviewDL().getTime()/1000));
				confInfo.setAttribute("startdateUTC", Long.toString(conf.getStartDate().getTime()/1000));
				confInfo.setAttribute("submitUTC", Long.toString(conf.getSubmissionDL().getTime()/1000));
				confInfo.setAttribute("name", conf.getConfName());
				confInfo.setAttribute("location", conf.getLocation());
				confInfo.setAttribute("size", Integer.toString(conf.getSize()));
			} else {  // THERE NO USER FOUND
				System.err.println("There was no conference by that ID found.");
				return false;
			}
		} else {
			//CREATE A NEW CONFERENCE
			String confQ = "//data/conferences";

			NodeList confData = null;
			try {
				confData = getNode(confQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			//Check to see if there are confrences
			if (confData.getLength() > 0) {
				Element confRoot = (Element) confData.item(0);
				Element newconf = doc.createElement("conference");
				newconf.setAttribute("decisionUTC", Long.toString(conf.getDecisionDL().getTime()/1000));
				newconf.setAttribute("enddateUTC", Long.toString(conf.getEndDate().getTime()/1000));
				newconf.setAttribute("reccomendUTC", Long.toString(conf.getRecommendationDL().getTime()/1000));
				newconf.setAttribute("reviewUTC", Long.toString(conf.getReviewDL().getTime()/1000));
				newconf.setAttribute("startdateUTC", Long.toString(conf.getStartDate().getTime()/1000));
				newconf.setAttribute("submitUTC", Long.toString(conf.getSubmissionDL().getTime()/1000));
				newconf.setAttribute("name", conf.getConfName());
				int nextConf = nextConferenceID();
				newconf.setAttribute("id", Integer.toString(nextConf));
				newconf.setAttribute("location", conf.getLocation());
				newconf.setAttribute("size", Integer.toString(conf.getSize()));
				confRoot.appendChild(newconf);
				conf.setConfID(nextConf);
			} else {
				return false;
			}

		}

		setChanged();
		notifyObservers();

		return saveDOM();
	}

	/**
	 * Writes to persistence a role assignment for a user and conference.
	 * @param confID The Conference ID associated with the role.
	 * @param userID The UserID in which to assign the role.
	 * @param role The role to assign to the user.
	 * @return Will return <code>true</code> if the assignment was successfully
	 * written to the parser, <code>false</code> otherwise.
	 */
	public boolean writeAssignment(int confID, int userID, Role role)
	{

			//CREATE A NEW ASSIGNMENT
			String assnQ = "//data/assignments";

			NodeList assnData = null;
			try {
				assnData = getNode(assnQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			//Check to see if there are confrences
			if (assnData.getLength() > 0) {
				Element assnRoot = (Element) assnData.item(0);
				Element newAssn = doc.createElement("assignment");
				newAssn.setAttribute("confID", Integer.toString(confID));
				newAssn.setAttribute("roleID", Integer.toString(role.ordinal()));
				newAssn.setAttribute("userID", Integer.toString(userID));
				newAssn.setAttribute("id", Integer.toString(nextAssignmentID()));
				assnRoot.appendChild(newAssn);
			} else {
				return false;
			}

		return saveDOM();
	}
	
	/**
	 * Write Manuscript will take any paper, new or otherwise and make any changes to the filesystem as necessary.
	 * @param the_manuscript The manuscript object to write to persistence.
	 * @return Will return <code>true</code> if successful, <code>false</code> otherwise.  
	 */
	public boolean writeManuscript(Manuscript the_manuscript)
	{
		if (the_manuscript.getMy_id() != FileParser.NEW_ELEMENT)
		{  //WE ARE UPDATING AN EXISTING FILE WRITE THE FILE TO PERSISTENCE AND UPDATE DATA.XML
			String papQuery = "//data/papers/paper[@id="+the_manuscript.getMy_id()+"]";
			NodeList papNodes = null;
			
			try {
				papNodes = getNode(papQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}
			
			if (papNodes.getLength() > 0) //MODIFY THE EXISTING PAPER
			{
				Element papInfo = (Element) papNodes.item(0);
				papInfo.setAttribute("abstract", the_manuscript.getMy_abstract());
				papInfo.setAttribute("userID", Integer.toString(the_manuscript.getMy_author()));
				papInfo.setAttribute("title", the_manuscript.getMy_title());
				String properFileName = "MANU"+the_manuscript.getMy_id()+"."+getExtension(the_manuscript.getFile());
				papInfo.setAttribute("statusID", Integer.toString(the_manuscript.getMy_status().ordinal()));
				papInfo.setAttribute("path", properFileName);
				File newFile = new File(properFileName);

				
				if (!the_manuscript.getFile().getAbsolutePath().equals(newFile.getAbsolutePath()))
				{ //ONLY COPY IF IT IS A NEW FILE.
					
					try {
						copyFile(the_manuscript.getFile(), newFile);
					} catch (IOException e) {
						System.err.println("The Files Could Not Be Copied");
						e.printStackTrace();
						return false;
					}
					papInfo.setAttribute("path", properFileName);
					the_manuscript.setFile(newFile);
				}
			}
			
		} else { //THIS IS A NEW FILE AND WE MUST TREAT IT LIKE ONE.
			String paperQ = "//data/papers";

			NodeList paperData = null;
			try {
				paperData = getNode(paperQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			//Check to see if there is a PAPERS node to add paper to
			if (paperData.getLength() > 0) {
				Element paperRoot = (Element) paperData.item(0);
				Element newPaper = doc.createElement("paper");
				int nextID = nextPaperID();
				newPaper.setAttribute("abstract", the_manuscript.getMy_abstract());
				newPaper.setAttribute("authorID", Integer.toString(the_manuscript.getMy_author()));
				newPaper.setAttribute("confID", Integer.toString(the_manuscript.getMy_conference()));
				newPaper.setAttribute("statusID", Integer.toString(the_manuscript.getMy_status().ordinal()));
				newPaper.setAttribute("title", the_manuscript.getMy_title());
				newPaper.setAttribute("spcID", Integer.toString(the_manuscript.getMy_spc()));
				newPaper.setAttribute("id", Integer.toString(nextID));

				String properFileName = "MANU"+nextID+"."+getExtension(the_manuscript.getFile());
				newPaper.setAttribute("path", properFileName);

				File newFile = new File(properFileName);
				try {
					copyFile(the_manuscript.getFile(), newFile);
				} catch (IOException e) {
					e.printStackTrace();
					System.err.println("Couldn't copy the file");
					return false;
				}
				
				the_manuscript.setFile(newFile);
				the_manuscript.setID(nextID);
				paperRoot.appendChild(newPaper);
				//TODO DEAL WITH CREATING A NEW FILE AT THE PROPERFILENAMELOCATION.
			} else {
				return false;
			}
		}
		
		setChanged();
		notifyObservers();

		return saveDOM();
	}

	/**
	 * Write Review will take any review, new or otherwise and make any changes to the filesystem as necessary.
	 * @param the_review The review object to write to persistence.
	 * @return Will return <code>true</code> if successful, <code>false</code> otherwise.  
	 */
	public boolean writeReview(Review the_review)
	{
		if (the_review.getMy_id() != FileParser.NEW_ELEMENT)
		{  //WE ARE UPDATING AN EXISTING FILE WRITE THE FILE TO PERSISTENCE AND UPDATE DATA.XML
			String revQuery = "//data/reviews/review[@id="+the_review.getMy_id()+"]";
			NodeList revNodes = null;
			
			try {
				revNodes = getNode(revQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}
			
			if (revNodes.getLength() > 0) //MODIFY THE EXISTING REVIEW
			{
				Element revInfo = (Element) revNodes.item(0);
				
				String properFileName = "REV"+the_review.getMy_id()+getExtension(the_review.getFile());
				File newFile = new File(properFileName);
				
				try {
					copyFile(the_review.getFile(), newFile);
				} catch (IOException e) {
					System.err.println("The Files Could Not Be Copied");
					e.printStackTrace();
					return false;
				}
				
				revInfo.setAttribute("path", properFileName);
				the_review.setFile(newFile);
			}
			
		} else { //THIS IS A NEW FILE AND WE MUST TREAT IT LIKE ONE.
			String revQ = "//data/reviews";

			NodeList revData = null;
			try {
				revData = getNode(revQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			//Check to see if there is a Reviews node to add review to
			if (revData.getLength() > 0) {
				Element paperRoot = (Element) revData.item(0);
				Element newrev = doc.createElement("review");
				int nextID = nextReviewID();
				newrev.setAttribute("paperID", Integer.toString(the_review.getMy_paperID()));
				newrev.setAttribute("userID", Integer.toString(the_review.getMy_reviewerID()));
				newrev.setAttribute("id", Integer.toString(nextID));

				String properFileName = "REV"+nextID+"."+getExtension(the_review.getFile());
				newrev.setAttribute("path", properFileName);

				File newFile = new File(properFileName);
				try {
					copyFile(the_review.getFile(), newFile);
				} catch (IOException e) {
					e.printStackTrace();
					System.err.println("Couldn't copy the file");
					return false;
				}
				
				the_review.setFile(newFile);
				the_review.setID(nextID);
				paperRoot.appendChild(newrev);
			} else {
				return false;
			}
		}
		
		setChanged();
		notifyObservers();

		return saveDOM();
	}	
	
	
	/**
	 * Write Review will take any review, new or otherwise and make any changes to the filesystem as necessary.
	 * @param the_review The review object to write to persistence.
	 * @return Will return <code>true</code> if successful, <code>false</code> otherwise.  
	 */
	public boolean writeRecommendation(Recommendation the_rec)
	{
		if (the_rec.getMy_id() != FileParser.NEW_ELEMENT)
		{  //WE ARE UPDATING AN EXISTING FILE WRITE THE FILE TO PERSISTENCE AND UPDATE DATA.XML
			String recQuery = "//data/recommendations/recommendation[@id="+the_rec.getMy_id()+"]";
			NodeList recNodes = null;
			
			try {
				recNodes = getNode(recQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}
			
			if (recNodes.getLength() > 0) //MODIFY THE EXISTING REVIEW
			{
				Element recInfo = (Element) recNodes.item(0);

				
				String properFileName = "RECO"+the_rec.getMy_id()+getExtension(the_rec.getFile());
				File newFile = new File(properFileName);
				
				try {
					copyFile(the_rec.getFile(), newFile);
				} catch (IOException e) {
					System.err.println("The Files Could Not Be Copied");
					e.printStackTrace();
					return false;
				}
				
				recInfo.setAttribute("path", properFileName);
				the_rec.setFile(newFile);
			}
			
		} else { //THIS IS A NEW FILE AND WE MUST TREAT IT LIKE ONE.
			String recQ = "//data/recommendations";

			NodeList recData = null;
			try {
				recData = getNode(recQ);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
				return false;
			}

			//Check to see if there is a Reviews node to add review to
			if (recData.getLength() > 0) {
				Element recRoot = (Element) recData.item(0);
				Element newrec = doc.createElement("recommendation");
				int nextID = nextRecommendationID();

				newrec.setAttribute("paperID", Integer.toString(the_rec.getMy_paperID()));
				newrec.setAttribute("userID", Integer.toString(the_rec.getMy_spcID()));
				newrec.setAttribute("id", Integer.toString(nextID));
				
				String properFileName = "RECO"+nextID+"."+getExtension(the_rec.getFile());
				newrec.setAttribute("path", properFileName);
				newrec.setAttribute("path", properFileName);
				newrec.setAttribute("recValue", the_rec.getMy_recValue());
				File newFile = new File(properFileName);
				try {
					copyFile(the_rec.getFile(), newFile);
				} catch (IOException e) {
					e.printStackTrace();
					System.err.println("Couldn't copy the file");
					return false;
				}
				
				the_rec.setFile(newFile);
				the_rec.setID(nextID);
				recRoot.appendChild(newrec);
			} else {
				return false;
			}
		}
		
		setChanged();
		notifyObservers();

		return saveDOM();
	}	
	
	/**
	 * Removes a manuscript from persistence
	 * @param The manuscript to remove.
	 * @return Will return <code>true</code> if the manscript removed, <code>false</code> otherwise.
	 */
	public boolean removeManuscript(Manuscript the_manu)
	{
		if (the_manu.getMy_id() != FileParser.NEW_ELEMENT)
		{
			String papQuery = "//data/papers/paper[@id="+the_manu.getMy_id()+"]";
			NodeList papNodes = null;

			try {
				papNodes = getNode(papQuery);
			} catch (XPathExpressionException e) {
				e.printStackTrace();
			}

			if (papNodes.getLength() > 0)
			{
				Node paperElement = papNodes.item(0);
				Node parentElement = paperElement.getParentNode(); 
				parentElement.removeChild(paperElement);
				setChanged();
				notifyObservers();

				return saveDOM();
			} else {
				System.err.println("NO manuscript to remove.");
				return false;
			}		
		} else {
			return false; //THIS IS A NEW PAPER, CAN'T BE REMOVED.
		}
	}
	
	
	/**
	 * Modifies a paper in persistence to store/modify an SPC.
	 * @param the_paper The paper to mofidy.
	 * @param spcID The avalue of the spcID to set on the paper.
	 * @return Will return <code>true</code> if the paper was successfully modified.
	 * Otherwise will return <code>false</code>.
	 */
	public boolean setSPC(Manuscript the_paper, int spcID)
	{
		
		int paperID = the_paper.getMy_id();
		String papQuery = "//data/papers/paper[@id="+paperID+"]";
		NodeList papNodes = null;
		
		try{
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();			
		}

		if(papNodes.getLength() > 0) {
			//THERE WAS A USERNODE FOUND, UPDATE IT
			Element paper = (Element) papNodes.item(0);
			paper.setAttribute("spcID", Integer.toString(spcID));
		} else {
			System.err.println("No User By that ID was found, cannot update");
			return false;
		}
		setChanged();
		notifyObservers();

		return saveDOM();
	}
	
	/**
	 * Add a Reviewer to a paper and save in persistence.
	 * @param the_paper The paper for which the reviewer needs assigned.
	 * @param userID The ID of the user that will be assigned at that paper's reviewer.
	 * @return Returns <code>true</code> if the paper is successfully modified, <code>false</code> otherwise.
	 */
	public boolean addReviewer(Manuscript the_paper, int userID) {
		int paperID = the_paper.getMy_id();
		String papQuery = "//data/papers/paper[@id="+paperID+"]";
		NodeList papNodes = null;
		
		try{
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();			
		}

		if(papNodes.getLength() > 0) {
			//THERE WAS A USERNODE FOUND, UPDATE IT
			Element revRoot = (Element) papNodes.item(0);
			Element newreviewer = doc.createElement("reviewer");
			newreviewer.setAttribute("userID", Integer.toString(userID));
			newreviewer.setAttribute("paperID", Integer.toString(paperID));
			revRoot.appendChild(newreviewer);
		} else {
			System.err.println("No Paper By that ID was found, cannot update");
			return false;
		}
		setChanged();
		notifyObservers();

		return saveDOM();
	}

	/**
	 * Returns a Manuscript object associated with a specific ID
	 * @param id The ID of the manuscript to search for.
	 * @return A Manuscript associated with that ID.  If no papers are found, will return <code>null</code>
	 */
	private Manuscript getPaperWithID(int id)
	{
		String papQuery = "//data/papers/paper[@id="+id+"]";
		NodeList papNodes = null;

		try{
			papNodes = getNode(papQuery);
		} catch (XPathExpressionException e) {
			e.printStackTrace();			
		}

		if (papNodes.getLength() > 0)
		{
			NamedNodeMap papInfo = papNodes.item(0).getAttributes();

			int authorID = Integer.parseInt(papInfo.getNamedItem("authorID").getNodeValue());
			Status statusID = Status.values()[Integer.parseInt(papInfo.getNamedItem("statusID").getNodeValue())];
			int confID =   Integer.parseInt(papInfo.getNamedItem("confID").getNodeValue());
			String title = papInfo.getNamedItem("title").getNodeValue();
			String abst = papInfo.getNamedItem("abstract").getNodeValue();
			String path = papInfo.getNamedItem("path").getNodeValue();
			int spc = Integer.parseInt(papInfo.getNamedItem("spcID").getNodeValue());

			return new Manuscript(id, authorID, statusID, confID, title, abst, path, spc);

		
		} else {
			System.err.println("There was no by that ID");
			return null;
		}
	}

	/**
	 * Returns a Conference Object associated with the supplied ID.
	 * @param id THe conference ID.
	 * @return Returns a conference object associated with the supplied id.
	 * <code>null</code> will be returned if no conference exists.
	 */
	private Conference getConfWithID(int id)
	{
		Conference toRet = null;
	
		String confQuery = "//data/conferences/conference[@id="+id+"]";
		NodeList confNodes = null;
		try {
			confNodes = getNode(confQuery);
		} catch (XPathExpressionException e) {
			//TODO Throw error of query is bad.
			e.printStackTrace();
		}
	
		if (confNodes.getLength() > 0) {
			NamedNodeMap confInfo = confNodes.item(0).getAttributes();
			
			String confName = confInfo.getNamedItem("name").getNodeValue().toString();	
			Date startDate = nodeToDate(confInfo.getNamedItem("startdateUTC"));
			Date endDate = nodeToDate(confInfo.getNamedItem("enddateUTC"));
			Date decisionDL = nodeToDate(confInfo.getNamedItem("decisionUTC"));
			Date recommendationDL = nodeToDate(confInfo.getNamedItem("reccomendUTC"));
			Date reviewDL = nodeToDate(confInfo.getNamedItem("reviewUTC"));
			Date submissionDL = nodeToDate(confInfo.getNamedItem("submitUTC"));
			String location = confInfo.getNamedItem("location").getNodeValue().toString();
			int size = Integer.parseInt(confInfo.getNamedItem("size").getNodeValue().toString());
			toRet = new Conference(id, confName, startDate, endDate, decisionDL, recommendationDL, reviewDL, submissionDL, location, size);	
	
			return toRet;
		} else { //I FOUND NOTHING SO RETURN NOTHING
			return null;
		}
	}

	/**
	 * Takes what exists currently in DOM and write it back to current_filepath.
	 * @return <code>true</code> if the file is successfully saved, <code>false</code> otherwise.
	 */
	private boolean saveDOM()
	{
		Transformer transformer = null; 
		try {
			transformer = TransformerFactory.newInstance().newTransformer();
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("Can't construct Transformer");
			return false;
		}

		Result output = new StreamResult(new File(current_data_path));
		Source input = new DOMSource(doc);
		try {
			transformer.transform(input, output);
		} catch (TransformerException e) {
			e.printStackTrace();
			System.err.println("Can't save file");
			return false;
		}

		return true;
	}


	/**
	 * A small helper method that takes a node and turns it into a UTC Date
	 * @param The node object that contains the date to convert.
	 * @return A Data value associated with that UTC Time.
	 */
	private Date nodeToDate(Node node)
	{
		long utc = Long.parseLong(node.getNodeValue().toString()) * 1000;
		return new Date(utc);
	}

	/**
	 * Returns a List of Nodes as a result of the provided query.
	 * @param query The query string that one would like to compile.
	 * @return A compiled version of the query string.
	 * @throws XPathExpressionException If the query cannot be compiled, this method will throw an exception.
	 */
	private NodeList getNode(String query) throws XPathExpressionException
	{
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(query);

		Object result = null;
		try {
			result = expr.evaluate(doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}

		NodeList nodes = (NodeList) result;

		return nodes;
	}

	/**
	 * Returns a List of Nodes as a result of the provided query.
	 * @param query The query string that one would like to compile.
	 * @return A compiled version of the query string.
	 * @throws XPathExpressionException If the query cannot be compiled, this method will throw an exception.
	 */
	private String getValue(String query, String attribute) throws XPathExpressionException
	{
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(query);

		Object result = null;
		try {
			result = expr.evaluate(doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		NodeList nodes = (NodeList) result;

		Element e = (Element) nodes.item(0); 

		return e.getAttribute(attribute);
	}

	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for a user.
	 */
	private int nextUserID()
	{
		String uQ = "//data/users/user";
		return nextID(uQ);
	}
	
	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for an assignment.
	 */
	private int nextAssignmentID()
	{
		String aQ = "//data/assignments/assignment";
		return nextID(aQ);			
	}
	
	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for a conference.
	 */
	private int nextConferenceID()
	{
		String cQ = "//data/conferences/conference";
		return nextID(cQ);
	}
	
	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for a paper.
	 */
	private int nextPaperID()
	{
		String pQ = "//data/papers/paper";
		return nextID(pQ);
	}

	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for a recommendation.
	 */
	private int nextRecommendationID()
	{
		String rQ = "//data/recommenations/recommendation";
		return nextID(rQ);
	}
	
	/**
	 * Searches for the highest ID value and returns the next available integer.
	 * @return The next highest ID value for a review.
	 */
	private int nextReviewID()
	{
		String rQ = "//data/reviews/review";
		return nextID(rQ);
	}
	
	/**
	 * Searches for the highest ID value returned for the provided xPath query.
	 * @return The next highest integer for an ID field returned by the xPath query provided.
	 * If the xPath query is bad will return -1.
	 */
	private int nextID(String query)
	{
		NodeList nodes = null;

		try {
			nodes = getNode(query);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
			return -1;
		}

		int min = 0;
		for (int i = 0; i < nodes.getLength(); i++)
		{
			Element user = (Element) nodes.item(i);
			int val = Integer.parseInt(user.getAttribute("id"));
			if (min < val)
				min = val;
		}

		return min + 1;
	}
	
	/**
	 * Returns the extension of the filename.
	 * @param the_file The File whose extension is desired.
	 * @return The final characters after the last period, or an empty string if no period exists in filename.
	 */
	private String getExtension(File the_file)
	{
		String name = the_file.getName();
		int pos = name.lastIndexOf('.');
		if (pos == -1)
			return "";
		return name.substring(pos+1);
	}
	/**
	 * Copies the sourceFile over the destFile on the user's local file system.
	 * @param sourceFile The file to use as source.
	 * @param destFile The file to use as destination.  This file will be destroyed.
	 * @throws IOException If the files are non existent or the destination file is locked by the
	 * file system.
	 */
	private static void copyFile(File sourceFile, File destFile) throws IOException {
	    if(!destFile.exists()) {
	        destFile.createNewFile();
	    }

	    FileChannel source = null;
	    FileChannel destination = null;

	    try {
	        source = new FileInputStream(sourceFile).getChannel();
	        destination = new FileOutputStream(destFile).getChannel();
	        destination.transferFrom(source, 0, source.size());
	    }
	    finally {
	        if(source != null) {
	            source.close();
	        }
	        if(destination != null) {
	            destination.close();
	        }
	    }
	}
}